namespace SimpleWebServices.Security.Principal
{
    using System;
    using System.Collections.Specialized;
    using System.Net;
    using System.Web;
    using System.Xml.XPath;
    using Moq;
    using SimpleWebServices;
    using SimpleWebServices.Data;
    using SimpleWebServices.Security.Cryptography;
    using SimpleWebServices.Web;
    using Xunit;

    public sealed class AuthenticationRecordOfTFacts
    {
        [Fact]
        public void typedef()
        {
            Assert.True(new ObjectExpectations<AuthenticationRecord<TestUser>>()
                .DerivesFrom<SimpleWebServices.Data.Record>()
                .IsConcreteClass()
                .IsSealed()
                .HasDefaultConstructor()
                .XmlRoot("authentication")
                .Result);
        }

        [Fact]
        public void deserialize()
        {
            AuthenticationRecord<TestUser> obj = (
                "<authentication>" +
                    "<user urn='urn://user/d518987f-be0e-48b2-b3ef-87276b8c3546' />" +
                    "<data>" +
                        "<value name='ip'>127.0.0.1</value>" +
                    "</data>" +
                "</authentication>").Deserialize<AuthenticationRecord<TestUser>>();

            Assert.Equal<string>("127.0.0.1", obj.Data["ip"]);
            Assert.Equal<string>("urn://user/d518987f-be0e-48b2-b3ef-87276b8c3546", obj.User.Urn);
        }

        [Fact]
        public void deserializeEmpty()
        {
            AuthenticationRecord<TestUser> actual = "<authentication />".Deserialize<AuthenticationRecord<TestUser>>();

            Assert.NotNull(actual.Data);
            Assert.Null(actual.User);
        }

        [Fact]
        public void serialize()
        {
            AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
            {
                User = new Link<TestUser>("urn://user/" + Token.New())
            };
            obj.Data.Add("ip", "127.0.0.1");

            XPathNavigator navigator = obj.Serialize().CreateNavigator();

            Assert.True((bool)navigator.Evaluate("1=count(/authentication/user[@urn='" + obj.User.Urn + "'])"));
            Assert.True((bool)navigator.Evaluate("'127.0.0.1'=/authentication/data/value[@name='ip']"));
        }

        [Fact]
        public void ctor()
        {
            Assert.NotNull(new AuthenticationRecord<TestUser>());
        }

        [Fact]
        public void prop_Entity()
        {
            Assert.True(new PropertyExpectations(typeof(AuthenticationRecord<TestUser>).GetProperty("Entity"))
                .XmlIgnore()
                .TypeIs<string>()
                .DefaultValueIsNotNull()
                .Result);
        }

        [Fact]
        public void prop_Entity_get()
        {
            AuthenticationRecord<TestUser> obj = (
                "<authentication>" +
                    "<data>" +
                        "<value name='ip'>127.0.0.1</value>" +
                    "</data>" +
                "</authentication>").Deserialize<AuthenticationRecord<TestUser>>();

            string expected = "NGf9vGl2umFMjlWJk0qj9g==";
            string actual = MD5Hash.Compute(obj.Entity);

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void prop_Entity_getEmpty()
        {
            string expected = string.Empty;
            string actual = new AuthenticationRecord<TestUser>().Entity;

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void prop_User()
        {
            Assert.True(new PropertyExpectations(typeof(AuthenticationRecord<TestUser>).GetProperty("User"))
                .XmlElement("user")
                .IsAutoProperty<Link<TestUser>>()
                .Result);
        }

        [Fact]
        public void op_ToHttpCookie_boolFalse_whenHTTP_X_FORWARDED_FOR()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection { { "HTTP_X_FORWARDED_FOR", "127.0.0.1" } }).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };

                HttpCookie actual = obj.ToHttpCookie(false);

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(1), actual.Expires);
                Assert.Equal<string>("Authentication", actual.Name);
                Assert.Equal<string>(obj.Urn, actual.Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_ToHttpCookie_boolFalse_whenREMOTE_ADDR()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection { { "REMOTE_ADDR", "127.0.0.1" } }).Verifiable();
                HttpContextFactory.Mock = mock.Object;

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };

                HttpCookie actual = obj.ToHttpCookie(false);

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(1), actual.Expires);
                Assert.Equal<string>("Authentication", actual.Name);
                Assert.Equal<string>(obj.Urn, actual.Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_ToHttpCookie_boolFalse_whenUserHostAddress()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection()).Verifiable();
                mock.SetupGet<string>(x => x.Request.UserHostAddress).Returns("127.0.0.1").Verifiable();
                HttpContextFactory.Mock = mock.Object;

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };

                HttpCookie actual = obj.ToHttpCookie(false);

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));

                Assert.Equal<DateTime>(DateTimeFactory.UtcNow.AddDays(1), actual.Expires);
                Assert.Equal<string>("Authentication", actual.Name);
                Assert.Equal<string>(obj.Urn, actual.Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_ToHttpCookie_boolTrue()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                var mock = new Mock<HttpContextBase>();
                mock.SetupGet<NameValueCollection>(x => x.Request.ServerVariables).Returns(new NameValueCollection()).Verifiable();
                mock.SetupGet<string>(x => x.Request.UserHostAddress).Returns("127.0.0.1").Verifiable();
                HttpContextFactory.Mock = mock.Object;

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };

                HttpCookie actual = obj.ToHttpCookie(true);

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));

                Assert.Equal<DateTime>(DateTime.MinValue, actual.Expires);
                Assert.Equal<string>("Authentication", actual.Name);
                Assert.Equal<string>(obj.Urn, actual.Value);

                mock.VerifyAll();
            }
            finally
            {
                DateTimeFactory.Reset();
                HttpContextFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Store_whenInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    Token = Token.New(),
                    Urn = "urn://authentication/" + Token.New(),
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };
                obj.Data.Add("ip", "127.0.0.1");

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>(new
                {
                    token = obj.Token,
                    urn = obj.Urn,
                    created = DateTimeFactory.UtcNow.AddDays(-2),
                    modified = DateTimeFactory.UtcNow.AddDays(-1),
                    status = (int)HttpStatusCode.OK,
                    cacheability = (int)HttpCacheability.Server,
                    etag = MD5Hash.Compute(string.Empty).ToString(),
                    xml = "<authentication />" 
                });

                obj.Store();

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.AddDays(-2).ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));
            }
            finally
            {
                DateTimeFactory.Reset();
                FakeRepository.Reset();
            }
        }

        [Fact]
        public void op_Store_whenNotInRepository()
        {
            FakeRepository.Clear();

            try
            {
                DateTimeFactory.Mock = DateTime.UtcNow;

                FakeRepository authentications = FakeRepository.Register<AuthenticationRecord<TestUser>>();

                AuthenticationRecord<TestUser> obj = new AuthenticationRecord<TestUser>
                {
                    User = new Link<TestUser>("urn://user/" + Token.New())
                };
                obj.Data.Add("ip", "127.0.0.1");

                obj.Store();

                string xpath =
                    "1=count(" +
                        "/repository" +
                        "/object" +
                            "[@token]" +
                            "[@urn]" +
                            "[@created='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@modified='" + DateTimeFactory.UtcNow.ToXmlString() + "']" +
                            "[@status='" + (int)HttpStatusCode.OK + "']" +
                            "[@cacheability='" + (int)HttpCacheability.Server + "']" +
                            "[@etag='" + MD5Hash.Compute(obj.Entity) + "']" +
                        "/authentication" +
                        "/data" +
                        "/value" +
                            "[@name='ip']" +
                            "[text()='127.0.0.1']" +
                    ")";

                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate(xpath));
                Assert.True((bool)authentications.Xml.CreateNavigator().Evaluate("1=count(/repository/object/authentication/user[@urn='" + obj.User.Urn + "'])"));
            }
            finally
            {
                DateTimeFactory.Reset();
                FakeRepository.Reset();
            }
        }
    }
}